package kz.pompei.ifacer.dialogs;

import static kz.pompei.ifacer.dialogs.DiaUtil.extractWindow;
import static kz.pompei.ifacer.util.Util.floatToStr;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.swing.AbstractListModel;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import kz.pompei.ifacer.util.Handler;
import kz.pompei.ifacer.util.HandlerList;

public class FontChooserDialog {
  private JPanel pane;
  private GridBagConstraints gbc;
  private final Component parentComponent;
  private JDialog dia;
  public String dialogName = "CommonFontChooserDialog";
  private String fontFamily;
  public String selectedFontFamily;
  public float selectedSize;
  private StrGetter searchText;
  private List<Row> listData = null;
  private final RowDataModel rowDataModel = new RowDataModel();
  private final HandlerList updates = new HandlerList();
  private FloatAcceptor size;
  private StrGetter exampleText;
  
  private static SortedSet<Row> allFontFamilies = new TreeSet<>();
  private JList<Row> jlist;
  private JLabel searchLabel;
  private JLabel sizeLabel;
  private JPanel examplesPanel;
  
  public FontChooserDialog(Component parentComponent) {
    this.parentComponent = parentComponent;
    initPane();
  }
  
  private void initPane() {
    pane = new JPanel(new GridBagLayout());
    gbc = new GridBagConstraints();
    gbc.gridy = 0;
    gbc.anchor = GridBagConstraints.WEST;
    
    addSearchLabel();
    
    addSizeLebal();
    
    gbc.gridy++;
    
    addSearchField();
    
    addSizeField();
    
    gbc.gridy++;
    
    addFontFamilyList();
    
    addSizeList();
    
    gbc.gridy++;
    
    addBtns();
    
    gbc.gridy++;
    
    addTextExampleField();
    
    gbc.gridy++;
    
    addTextExamplePanel();
  }
  
  private static String display(String str, String part) {
    if (str == null) return "";
    if (part == null) return "<html>" + str;
    part = part.trim().toUpperCase();
    if (part.length() == 0) return "<html>" + str;
    int index = str.toUpperCase().indexOf(part);
    if (index < 0) return "<html>" + str;
    
    return "<html>" + str.substring(0, index) + "<b>" + str.substring(index, index + part.length())
        + "</b>" + str.substring(index + part.length());
  }
  
  static {
    Font[] allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
    for (Font font : allFonts) {
      allFontFamilies.add(new Row(font.getFamily()));
    }
  }
  
  private static class Row implements Comparable<Row> {
    final String family;
    
    Row(String family) {
      this.family = family;
    }
    
    @Override
    public int compareTo(Row o) {
      if (family == o.family) return 0;
      if (family == null) return -1;
      if (o.family == null) return +1;
      return family.compareTo(o.family);
    }
    
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((family == null) ? 0 :family.hashCode());
      return result;
    }
    
    @Override
    public boolean equals(Object obj) {
      if (this == obj) return true;
      if (obj == null) return false;
      if (getClass() != obj.getClass()) return false;
      Row other = (Row)obj;
      if (family == null) {
        if (other.family != null) return false;
      } else if (!family.equals(other.family)) return false;
      return true;
    }
  }
  
  private class RowDataModel extends AbstractListModel<Row> {
    @Override
    public int getSize() {
      init();
      return listData.size();
    }
    
    private void init() {
      if (listData != null) return;
      listData = new ArrayList<>();
      for (Row row : allFontFamilies) {
        if (isInFilter(row.family)) listData.add(row);
      }
    }
    
    @Override
    public Row getElementAt(int index) {
      init();
      return listData.get(index);
    }
    
    public void fireChanged() {
      init();
      fireContentsChanged(this, 0, listData.size());
    }
  }
  
  private class RowRenderer extends JLabel implements ListCellRenderer<Row> {
    @Override
    public Component getListCellRendererComponent(JList<? extends Row> list, Row value, int index,
        boolean isSelected, boolean cellHasFocus) {
      
      if (isSelected) {
        setBackground(list.getSelectionBackground());
        setForeground(list.getSelectionForeground());
      } else {
        setBackground(list.getBackground());
        setForeground(list.getForeground());
      }
      
      setText(display(value.family, searchText.get()));
      setFont(list.getFont().deriveFont(Font.PLAIN));
      setOpaque(true);
      setEnabled(list.isEnabled());
      
      return this;
    }
    
  }
  
  private void addTextExamplePanel() {
    examplesPanel = new JPanel() {
      @Override
      public void paint(Graphics g) {
        paintExample((Graphics2D)g);
      }
    };
    examplesPanel.setLayout(null);
    JScrollPane scroll = new JScrollPane(examplesPanel);
    scroll.setPreferredSize(new Dimension(300, 200));
    gbc.gridx = 0;
    gbc.gridwidth = 2;
    pane.add(scroll, gbc);
    gbc.gridwidth = 1;
    
    updates.add(new Handler() {
      @Override
      public void handle() throws Exception {
        repaintExample();
      }
    });
  }
  
  private void addTextExampleField() {
    final JTextField txt = new JTextField();
    txt.setText("Пример текста");
    gbc.gridx = 0;
    gbc.gridwidth = 2;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    pane.add(txt, gbc);
    gbc.gridwidth = 1;
    
    exampleText = new StrGetter() {
      @Override
      public String get() {
        return txt.getText();
      }
    };
    
    txt.addKeyListener(new KeyAdapter() {
      @Override
      public void keyReleased(KeyEvent e) {
        repaintExample();
      }
    });
  }
  
  private void addBtns() {
    GridBagLayout lay = new GridBagLayout();
    lay.columnWidths = new int[] { 0, 10, 0 };
    lay.columnWeights = new double[] { 1, 1 };
    JPanel btns = new JPanel(lay);
    GridBagConstraints c = new GridBagConstraints();
    
    {
      final JButton b = new JButton("<html>OK");
      b.setPreferredSize(new Dimension(200, 50));
      c.insets = new Insets(0, 0, 0, 0);
      c.gridx = 0;
      c.gridy = 0;
      c.fill = GridBagConstraints.HORIZONTAL;
      btns.add(b, c);
      c.fill = GridBagConstraints.NONE;
      
      updates.add(new Handler() {
        @Override
        public void handle() throws Exception {
          if (fontFamily == null) {
            b.setText("<html><p align='center'>Нет выбранного шрифта");
          } else {
            b.setText("<html><p align='center'>Выбрать <br/>" + fontFamily + ", <nobr>разм. "
                + floatToStr(size.get()) + "</nobr>");
          }
        }
        
      });
      
      b.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          ok();
        }
      });
    }
    {
      c.insets = new Insets(0, 5, 0, 0);
      c.gridx = 1;
      c.gridy = 0;
      JButton b = new JButton("Отменить");
      b.setPreferredSize(new Dimension(120, 50));
      btns.add(b, c);
      b.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          dia.setVisible(false);
        }
      });
    }
    gbc.gridx = 0;
    gbc.gridwidth = 2;
    gbc.fill = GridBagConstraints.BOTH;
    pane.add(btns, gbc);
    gbc.fill = GridBagConstraints.NONE;
  }
  
  private void addSizeList() {
    class FloatDot {
      final float value;
      
      public FloatDot(float value) {
        this.value = value;
      }
      
      @Override
      public String toString() {
        return floatToStr(value);
      }
    }
    final FloatDot[] listData;
    {
      Float[] massive = new Float[] { 6f, 7f, 8f, 9f, 10f, 10.5f, 11f, 12f, 13f, 14f, 15f, 16f,
          18f, 20f, 22f, 24f, 26f, 28f, 32f, 36f, 40f, 44f, 48f, 54f, 60f, 66f, 72f, 80f, 88f, 96f };
      listData = new FloatDot[massive.length];
      for (int i = 0, C = massive.length; i < C; i++) {
        listData[i] = new FloatDot(massive[i]);
      }
    }
    final JList<FloatDot> jsizeList = new JList<>(listData);
    jsizeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    jsizeList.setVisibleRowCount(-1);
    jsizeList.setLayoutOrientation(JList.VERTICAL);
    jsizeList.setFont(jsizeList.getFont().deriveFont(Font.PLAIN));
    JScrollPane jsizeListScroll = new JScrollPane(jsizeList);
    jsizeListScroll.setPreferredSize(new Dimension(60, 200));
    gbc.insets = new Insets(1, 5, 5, 5);
    gbc.gridx = 1;
    gbc.gridwidth = 1;
    pane.add(jsizeListScroll, gbc);
    jsizeList.addListSelectionListener(new ListSelectionListener() {
      @Override
      public void valueChanged(ListSelectionEvent e) {
        FloatDot x = jsizeList.getSelectedValue();
        size.set(x.value);
        updates.fire();
      }
    });
  }
  
  private void addFontFamilyList() {
    jlist = new JList<>(rowDataModel);
    jlist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    jlist.setCellRenderer(new RowRenderer());
    jlist.setVisibleRowCount(-1);
    jlist.setLayoutOrientation(JList.VERTICAL);
    JScrollPane jlistScroll = new JScrollPane(jlist);
    jlistScroll.setPreferredSize(new Dimension(600, 200));
    gbc.insets = new Insets(1, 5, 5, 5);
    gbc.gridx = 0;
    gbc.gridwidth = 1;
    pane.add(jlistScroll, gbc);
    jlist.addListSelectionListener(new ListSelectionListener() {
      @Override
      public void valueChanged(ListSelectionEvent e) {
        Row value = jlist.getSelectedValue();
        if (value == null) {
          fontFamily = null;
        } else {
          fontFamily = value.family;
        }
        updates.fire();
      }
    });
    jlist.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        if (e.getClickCount() == 2) ok();
      }
    });
  }
  
  private void addSizeField() {
    gbc.insets = new Insets(1, 5, 1, 5);
    gbc.gridx = 1;
    gbc.fill = GridBagConstraints.BOTH;
    final JTextField sizeField = new JTextField();
    sizeLabel.setLabelFor(sizeField);
    pane.add(sizeField, gbc);
    gbc.fill = GridBagConstraints.NONE;
    sizeField.addKeyListener(new KeyAdapter() {
      @Override
      public void keyReleased(KeyEvent e) {
        updates.fire();
      }
    });
    size = new FloatAcceptor() {
      @Override
      public float get() {
        try {
          return Float.parseFloat(sizeField.getText());
        } catch (NumberFormatException e) {
          return 0;
        }
      }
      
      @Override
      public void set(float value) {
        sizeField.setText(floatToStr(value));
      }
    };
  }
  
  private void addSearchField() {
    gbc.insets = new Insets(1, 5, 1, 5);
    gbc.gridx = 0;
    gbc.fill = GridBagConstraints.BOTH;
    final JTextField searchField = new JTextField();
    searchLabel.setLabelFor(searchField);
    pane.add(searchField, gbc);
    gbc.fill = GridBagConstraints.NONE;
    this.searchText = new StrGetter() {
      @Override
      public String get() {
        return searchField.getText();
      }
    };
    searchField.addKeyListener(new KeyAdapter() {
      @Override
      public void keyReleased(KeyEvent e) {
        updates.fire();
        listData = null;
        jlist.clearSelection();
        rowDataModel.fireChanged();
      }
    });
  }
  
  private void addSizeLebal() {
    gbc.insets = new Insets(5, 5, 1, 5);
    gbc.gridx = 1;
    sizeLabel = new JLabel("Размер:");
    pane.add(sizeLabel, gbc);
  }
  
  private void addSearchLabel() {
    gbc.insets = new Insets(5, 5, 1, 5);
    gbc.gridx = 0;
    searchLabel = new JLabel("Найти:");
    pane.add(searchLabel, gbc);
  }
  
  private boolean isInFilter(String family) {
    String st = searchText.get();
    if (st == null) return true;
    st = st.trim().toUpperCase();
    if (st.length() == 0) return true;
    return family.toUpperCase().contains(st);
  }
  
  public void show(String fontFamily, float size) {
    this.fontFamily = fontFamily;
    selectedFontFamily = null;
    final Window window;
    dia = new JDialog(window = extractWindow(parentComponent), "Выбор шрифта",
        ModalityType.DOCUMENT_MODAL);
    dia.setContentPane(pane);
    this.size.set(size);
    selectedSize = size;
    
    DiaUtil.makeEscapeKey(dia);
    
    DiaUtil.put(dia, dialogName, window, 100, 100, 800, 600);
    
    if (fontFamily != null) {
      jlist.setSelectedValue(new Row(fontFamily), true);
    }
    dia.setVisible(true);
    dia.dispose();
  }
  
  private void ok() {
    selectedFontFamily = fontFamily;
    selectedSize = size.get();
    dia.setVisible(false);
  }
  
  private static class DrawLine {
    final String line;
    final int style;
    
    public DrawLine(String line, int style) {
      this.line = line;
      this.style = style;
    }
  }
  
  private void repaintExample() {
    examplesPanel.setPreferredSize(getExamplePanelSize());
    examplesPanel.repaint(300);
  }
  
  private Dimension getExamplePanelSize() {
    int width = 0;
    int height = 0;
    {
      float size = this.size.get();
      Font font = createFont();
      Graphics2D g = FontUtil.prepare(examplesPanel.getGraphics());
      for (DrawLine x : getLines()) {
        g.setFont(font.deriveFont(x.style).deriveFont(size));
        int w = g.getFontMetrics().stringWidth(x.line);
        if (width < w) width = w;
        height += 3 + g.getFontMetrics().getHeight();
      }
      g.dispose();
    }
    
    return new Dimension(width + 10, height + 10);
  }
  
  private Font createFont() {
    return new Font(fontFamily, Font.PLAIN, 1);
  }
  
  private void paintExample(Graphics2D g) {
    FontUtil.prepare(g);
    
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, examplesPanel.getWidth(), examplesPanel.getHeight());
    g.setColor(Color.BLACK);
    
    int y = 5;
    float size = this.size.get();
    Font font = createFont();
    
    for (DrawLine x : getLines()) {
      g.setFont(font.deriveFont(x.style).deriveFont(size));
      FontMetrics fm = g.getFontMetrics();
      g.drawString(x.line, 5, y + fm.getAscent());
      y += 3 + fm.getHeight();
    }
  }
  
  private List<DrawLine> getLines() {
    String text = exampleText.get();
    List<DrawLine> ret = new ArrayList<>();
    ret.add(new DrawLine("PLAIN - " + text, Font.PLAIN));
    ret.add(new DrawLine("BOLD - " + text, Font.BOLD));
    ret.add(new DrawLine("ITALIC - " + text, Font.ITALIC));
    ret.add(new DrawLine("BOLD ITALIC - " + text, Font.ITALIC + Font.BOLD));
    return ret;
  }
}
